package com.redxiii.tracplus.igen;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Index;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.NumericField;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.hibernate.Query;
import org.hibernate.classic.Session;
import org.hibernate.transform.Transformers;
import org.hibernate.type.StandardBasicTypes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.redxiii.tracplus.ejb.entity.Wiki;
import com.redxiii.tracplus.ejb.search.LuceneSearchManager;
import com.redxiii.tracplus.ejb.search.TracStuff;
import com.redxiii.tracplus.ejb.search.TracStuffField;

/**
 * @author Daniel Filgueiras
 * @since 19/08/2011
 */
public class LuceneIndexBuilder implements Runnable {

	private static final Logger logger = LoggerFactory.getLogger(LuceneIndexBuilder.class);

	private Session session;
	private IndexWriter indexWriter;
	
	public static void main(String[] args) {
		
		System.setProperty("jboss.server.temp.dir",args[0] + "/server/default/tmp");
		System.setProperty("jboss.server.config.url",args[0] + "/server/default/conf");
		
		File indexPathDir = new File(System.getProperty("jboss.server.temp.dir") + LuceneSearchManager.INDEX_DIRECTORY);
		if (!indexPathDir.exists()) {
			indexPathDir.mkdir();
		}
		
		new LuceneIndexBuilder().run();
	}
	
	public void run() {
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("TracPlusDS");
		EntityManager em = emf.createEntityManager();
		session = (Session) em.getDelegate();
		
		try {
			LuceneSearchManager.getInstance().delete();
			indexWriter = LuceneSearchManager.getInstance().createIndexWriter();
			
			rebuildWiki();
			indexWriter.commit();
			
			rebuildTicket();
			
			indexWriter.close();	// Commit included
		} catch (CorruptIndexException e) {
			logger.error("Error updating index",e);
		} catch (IOException e) {
			logger.error("Error updating index",e);
		}
	}
	
	@SuppressWarnings("unchecked")
	private void rebuildTicket() throws CorruptIndexException, IOException {
		
		logger.debug("Connecting and getting tickets");
		final Number lastTicketId = (Number) session
			.createSQLQuery("SELECT max(id) as ticket FROM trac.ticket")
			.addScalar("ticket", StandardBasicTypes.INTEGER)
			.uniqueResult();
			
		final Number firstTicketId = (Number) session
			.createSQLQuery("SELECT min(id) as ticket FROM trac.ticket")
			.addScalar("ticket", StandardBasicTypes.INTEGER)
			.uniqueResult();
			
		int ticketId = firstTicketId.intValue(), maxFetch = 1000;
		
		while (ticketId <= lastTicketId.intValue()) {
			logger.debug("Fetching tickets ids from {} to {}", ticketId, ticketId + maxFetch);
			
			Query query = session.createSQLQuery("SELECT ticket.*, ticket_change.newvalue, ticket_change.time as modified FROM trac.ticket left join trac.ticket_change " +
					"on (ticket.id = ticket_change.ticket and ticket_change.field = 'comment') " +
					"where ticket.id >= :ini and ticket.id < :end order by ticket.id, ticket.time, ticket_change.time")
					.setParameter("ini", ticketId)
					.setParameter("end", ticketId + maxFetch)
					.setResultTransformer(Transformers.aliasToBean(TicketQueryResult.class));
			
			List<TicketQueryResult> results = query.list();
			
			logger.debug("Got {} results",results.size());
			
			Map<Number,TracStuff> tickets = new HashMap<Number, TracStuff>();
			for (TicketQueryResult result : results) {
				
				Integer id = result.getId();
				TracStuff stuff = tickets.get(id);
				if (stuff == null) {
					stuff = new TracStuff(result);
					tickets.put(id, stuff);
				}
				if (result.getNewvalue() != null) {
					stuff.addContent(result.getNewvalue(), result.getModified());
				}
			}
			logger.debug("Got {} tickets", tickets.size());
			
			rebuildIndex(tickets.values());
			
			ticketId += maxFetch;
		}
	}
	
	@SuppressWarnings("unchecked")
	private void rebuildWiki() throws CorruptIndexException, IOException {
		
		logger.debug("Connecting and getting recent wiki pages");
		Query query = session.createSQLQuery(
				"SELECT name, max(version) as version FROM trac.wiki GROUP BY name")
				.setResultTransformer(Transformers.aliasToBean(RecentWiki.class));
		
		List<RecentWiki> recent = query.list();
		logger.debug("Fetching {} wiki pages", recent.size());
		
		int counter = 0;
		for (RecentWiki recentWiki : recent) {
			logger.trace("Fetching wiki page: {}", recentWiki.getName());
			
			query = session.createSQLQuery("SELECT * FROM trac.wiki WHERE name = :name and version = :version")
				.setParameter("name", recentWiki.getName())
				.setParameter("version", recentWiki.getVersion())
				.setResultTransformer(Transformers.aliasToBean(Wiki.class));
			
			Wiki wiki = (Wiki) query.uniqueResult();
			
			if (wiki == null) {
				logger.warn("Wiki page not found: {}", recentWiki);
			} else {
				rebuildIndex(new TracStuff(wiki));
			}
			counter++;
			
			if (counter % 250 == 0)
				logger.debug("Indexed {} pages", counter);
		}
	}
	
	private void rebuildIndex(Collection<TracStuff> wikis) throws CorruptIndexException, IOException {
		
		logger.debug("Updating index...");
		for (TracStuff stuff : wikis) {
			rebuildIndex(stuff);
		}
		indexWriter.commit();
		logger.debug("Done.");
	}
	
	private void rebuildIndex(TracStuff tracStuff) throws CorruptIndexException, IOException {
		
		logger.trace("Indexing: {} at {}", tracStuff.getId(), tracStuff.getCreatedDate());
		
		Document doc = new Document();
		
		doc.add(new Field(TracStuffField.CONTENT.toString(), tracStuff.getContent(), Store.NO, Index.ANALYZED));
		doc.add(new Field(TracStuffField.AUTHOR.toString(), tracStuff.getAuthor(), Store.YES, Index.ANALYZED));
		doc.add(new Field(TracStuffField.DESCRIPTION.toString(), tracStuff.getDescription(), Store.YES, Index.ANALYZED));
		doc.add(new Field(TracStuffField.CONTEXT.toString(), tracStuff.getContext(), Store.YES, Index.ANALYZED));
		doc.add(new Field(TracStuffField.URL.toString(), tracStuff.getUrl(), Store.YES, Index.NO));
		doc.add(new Field(TracStuffField.MODIFIED_DATE.toString(), tracStuff.getModifiedDate(), Store.YES, Index.NO));
		doc.add(new NumericField(TracStuffField.MODIFIED_TIMESTAMP.toString(), Store.NO, true).setLongValue(tracStuff.getModifiedTimestamp()));
		
		indexWriter.addDocument(doc);
	}
}
